package com.imooc.pan.server.modules.file.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.imooc.pan.core.exception.RPanBusinessException;
import com.imooc.pan.core.utils.FileUtils;
import com.imooc.pan.core.utils.IdUtil;
//import com.imooc.pan.server.common.mq.config.RabbitMQConfig;
import com.imooc.pan.server.common.mq.event.log.ErrorLogEvent;
import com.imooc.pan.server.modules.file.context.FileChunkMergeAndSaveContext;
import com.imooc.pan.server.modules.file.context.FileSaveContext;
import com.imooc.pan.server.modules.file.entity.RPanFile;
import com.imooc.pan.server.modules.file.entity.RPanFileChunk;
import com.imooc.pan.server.modules.file.service.IFileChunkService;
import com.imooc.pan.server.modules.file.service.IFileService;
import com.imooc.pan.server.modules.file.mapper.RPanFileMapper;
import com.imooc.pan.storage.engine.core.StorageEngine;
import com.imooc.pan.storage.engine.core.context.DeleteFileContext;
import com.imooc.pan.storage.engine.core.context.MergeFileContext;
import com.imooc.pan.storage.engine.core.context.StoreFileContext;
//import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lenovo
 * @description 针对表【r_pan_file(物理文件信息表)】的数据库操作Service实现
 * @createDate 2023-07-27 18:36:52
 */
@Service
public class FileServiceImpl extends ServiceImpl<RPanFileMapper, RPanFile>
        implements IFileService {

    @Autowired
    private StorageEngine storageEngine;

    @Autowired
    private IFileChunkService iFileChunkService;

//    @Autowired
//    private RabbitTemplate rabbitTemplate;

    //private ApplicationContext applicationContext;

    //@Autowired
    //@Qualifier(value = "defaultStreamProducer")
    //private IStreamProducer producer;

    //@Override
    //public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    //    this.applicationContext = applicationContext;
    //}

    /**
     * 上传单文件并保存实体记录
     * <p>
     * 1. 上传单文件
     * 2. 上下文保存实体记录，生成 RPanFile 对象并记录到数据库中，如果记录失败，上传的单文件也要删除
     *
     * @param context FileSaveContext
     */
    @Override
    public void saveFile(FileSaveContext context) {
        storeMultipartFile(context); // 上传单文件到文件存储引擎
        RPanFile record = doSaveFile(context.getFilename(), context.getRealPath(),
                context.getTotalSize(), context.getIdentifier(), context.getUserId());
        context.setRecord(record);
    }

    /**
     * 合并物理文件, 此时才算真正上传文件成功, 保存文件记录到 r_pan_file 表
     *
     * @param context FileChunkMergeAndSaveContext
     */
    @Override
    public void mergeFileChunkAndSaveFile(FileChunkMergeAndSaveContext context) {
        doMergeFileChunk(context);
        //
        RPanFile record = doSaveFile(context.getFilename(), context.getRealPath(),
                context.getTotalSize(), context.getIdentifier(), context.getUserId());
        context.setRecord(record);
    }

    /**
     * 委托文件存储引擎合并文件分片
     * <p>
     * 1、查询文件分片记录
     * 2、根据文件分片的记录合并物理文件
     * 3、删除文件分片记录
     * 4、合并真实存储路径到上下文信息中
     *
     * @param context
     */
    private void doMergeFileChunk(FileChunkMergeAndSaveContext context) {
        QueryWrapper<RPanFileChunk> queryWrapper = Wrappers.query();
        queryWrapper.eq("identifier", context.getIdentifier());
        queryWrapper.eq("create_user", context.getUserId());
        queryWrapper.ge("expiration_time", new Date());
        // 没有过期的文件分片
        List<RPanFileChunk> chunkRecordList = iFileChunkService.list(queryWrapper);
        if (CollectionUtils.isEmpty(chunkRecordList)) {
            throw new RPanBusinessException("该文件为找到分片记录");
        }
        // 找到所有的分片文件的存储路径
        List<String> realPathList =
                chunkRecordList.stream()
                        .sorted(Comparator.comparing(RPanFileChunk::getChunkNumber))
                        .map(RPanFileChunk::getRealPath)
                        .collect(Collectors.toList());
        // 存储引擎合并文件分片
        try {
            MergeFileContext mergeFileContext = new MergeFileContext();
            mergeFileContext.setFilename(context.getFilename());
            mergeFileContext.setIdentifier(context.getIdentifier());
            mergeFileContext.setUserId(context.getUserId());
            mergeFileContext.setRealPathList(realPathList);
            storageEngine.mergeFile(mergeFileContext);
            // 封装真实存储路径
            context.setRealPath(mergeFileContext.getRealPath());
        } catch (IOException e) {
            e.printStackTrace();
            throw new RPanBusinessException("文件分片合并失败");
        }
        // 数据库中删除文件分片记录
        List<Long> fileChunkRecordIdList = chunkRecordList
                .stream()
                .map(RPanFileChunk::getId)
                .collect(Collectors.toList());
        iFileChunkService.removeByIds(fileChunkRecordIdList);
    }

    /**
     * 保存文件实体
     * <p>
     * 单文件上传成功
     * 文件分片上传合并完成
     *
     * @param filename
     * @param realPath
     * @param totalSize
     * @param identifier
     * @param userId
     */
    private RPanFile doSaveFile(String filename, String realPath,
                                Long totalSize, String identifier, Long userId) {
        RPanFile record = assembleRPanFile(filename, realPath, totalSize, identifier, userId);
        if (!save(record)) { // 保存实体文件
            try {
                // 如果数据库保存失败，存储引擎需要删除上传的物理文件
                // storage-engine-core
                DeleteFileContext deleteFileContext = new DeleteFileContext();
                deleteFileContext.setRealFilePathList(Lists.newArrayList(realPath));
                storageEngine.delete(deleteFileContext);
            } catch (IOException e) {
                // 删除失败要广播一个事件
                e.printStackTrace();
                ErrorLogEvent errorLogEvent = new ErrorLogEvent("物理文件删除失败，请手动删除，文件路径为" + realPath, userId);

                // todo errorLogEvent
//                rabbitTemplate.convertAndSend(RabbitMQConfig.ERROR_LOG_EXCHANGE, "errorLog.send",
//                        JSON.toJSONString(errorLogEvent));
                //producer.sendMessage(PanChannels.ERROR_LOG_OUTPUT, errorLogEvent);
                //applicationContext.publishEvent(errorLogEvent);
            }
        }
        return record;
    }

    /**
     * 拼装 RPanFile 对象
     *
     * @param filename
     * @param realPath
     * @param totalSize
     * @param identifier
     * @param userId
     * @return
     */
    private RPanFile assembleRPanFile(String filename, String realPath, Long totalSize,
                                      String identifier, Long userId) {
        RPanFile record = new RPanFile(); // 文件信息表 r_pan_file
        record.setFileId(IdUtil.get()); // 生成一个文件 id，雪花算法
        record.setFilename(filename);
        record.setRealPath(realPath);
        record.setFileSize(String.valueOf(totalSize));
        record.setFileSizeDesc(FileUtils.byteCountToDisplaySize(totalSize));
        record.setFileSuffix(FileUtils.getFileSuffix(filename));
        record.setIdentifier(identifier);
        record.setCreateUser(userId);
        record.setCreateTime(new Date());
        return record;
    }

    /**
     * 上传单文件
     * 该方法可以委托文件存储引擎实现
     *
     * @param context
     */
    private void storeMultipartFile(FileSaveContext context) {
        try {
            // 文件存储引擎的上下文实体
            StoreFileContext storeFileContext = new StoreFileContext();
            storeFileContext.setInputStream(context.getFile().getInputStream());
            storeFileContext.setFilename(context.getFilename());
            storeFileContext.setTotalSize(context.getTotalSize());
            storageEngine.store(storeFileContext);
            context.setRealPath(storeFileContext.getRealPath());
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("文件上传失败");
        }
    }
}




